
package xj.graph2d.shapes.uml;

import static xj.graph2d.DrawShapeConstants.PointEastEdge;
import static xj.graph2d.DrawShapeConstants.cpady;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.awt.geom.Rectangle2D;

import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.DrawShapeConstants;
import xj.graph2d.Label;
import xj.graph2d.RectangleShape;
import xj.graph2d.layout.DiagramLayout;
import xj.graph2d.layout.LayoutParam;
import xj.graph2d.layout.PackageComparator;
import xj.graph2d.shapes.TitledShape;

import static xj.graph2d.DrawShapeUtil.moveShapes;
import static xj.graph2d.DrawShapeUtil.getShapeBounds;

public class PackageShape 
  extends TitledShape {

  public PackageShape() {
  }

  public PackageShape(float x, float y, float w, float h) {
    this(x, y, w, h, false, false);
  }

  public PackageShape(float x, float y, float w, float h, boolean leaf) {
    this(x, y, w, h, leaf, false);
  }

  public PackageShape(float x, float y, float w, float h,
		      boolean leaf, 
		      boolean createComp) {
    super(new RectangleShape(x, y + (leaf ? LEAF_TITLE_HEIGHT : CONTAINER_TITLE_HEIGHT),
			     w, h - (leaf ? LEAF_TITLE_HEIGHT : CONTAINER_TITLE_HEIGHT)),
	  new RectangleShape(x, y, 
			     w * 0.3f, (leaf ? LEAF_TITLE_HEIGHT : CONTAINER_TITLE_HEIGHT)), 
	  ABOVE_TOP_LEFT, x, y, w, h,
	  createComp);
    this.leaf = leaf;
  }

  public PackageShape(float x, float y, float w, float h, 
		      float tw, float th) { 
    this(x, y, w, h, tw, th, false, false);
  }

  public PackageShape(float x, float y, float w, float h, 
		      float tw, float th,
		      boolean leaf) { 
    this(x, y, w, h, tw, th, leaf, false);
  }

  public PackageShape(float x, float y, float w, float h, float tw, float th,
		      boolean leaf, 
		      boolean createComp) {
    super(new RectangleShape(x, y + th, w, h - th),
	  new RectangleShape(x, y, tw, th), 
	  ABOVE_TOP_LEFT, x, y, w, h,
	  createComp);
    this.leaf = leaf;
  }

  public PackageShape(DrawShape mainShape, DrawShape titleShape,
		      float x1, float y1, float x2, float y2,
		      boolean leaf, 
		      boolean createComp) {
    super(mainShape, titleShape, ABOVE_TOP_LEFT, x1, y1, x2, y2, createComp);
    this.leaf = leaf;
  }

  @Override
  public DrawShape makeInstance() 
    throws CloneNotSupportedException {
    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    return new PackageShape((mainShape != null) ? (DrawShape) mainShape.clone() : mainShape, 
			   (titleShape != null) ? (DrawShape) titleShape.clone() : titleShape, 
			    x1, y1, x2, y2, leaf,
			    comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s)
    throws CloneNotSupportedException {
    super.copyTo(s);
    if (s instanceof PackageShape) {
      PackageShape ss = (PackageShape) s;
      ss.leaf = leaf;
    }
  }

  @Override
  public String getNodeDisplayName() { 
    return getPackageName();
  }

  public String getPackageName() { 
    int role = (leaf ? DrawShapeConstants.ROLE_NODE_LABEL
		: DrawShapeConstants.ROLE_NODE_TITLE_LABEL);
    return UMLUtil.getName(this, role);
  }

  public void setPackageName(String name) { 
    int role = (leaf ? DrawShapeConstants.ROLE_NODE_LABEL
		: DrawShapeConstants.ROLE_NODE_TITLE_LABEL);
    UMLUtil.setName(this, role, name);
  }

  public String getFullPackageName() { 
    String name = getPackageName();
    if (parent != null && 
	(parent instanceof PackageShape)) { 
      PackageShape p = (PackageShape) parent; 
      String pname = p.getFullPackageName();
      if (pname != null) { 
	return pname + "." + name; 
      }
    }
    return name; 
  }

  public int getLevel() { 
    if (parent != null && 
	(parent instanceof PackageShape)) { 
      PackageShape p = (PackageShape) parent; 
      return p.getLevel() + 1; 
    }
    return 0;
  }

  public int getMaxDepth() { 
    java.util.List<PackageShape> subpkg =  getSubPackages();
    if (subpkg != null && 
	subpkg.size() > 0) { 
      int d = 0; 
      for (PackageShape pkg : subpkg) { 
	d = Math.max(d, pkg.getMaxDepth()); 
      }
      return d + 1; 
    }
    return 1; 
  }

  public int getSubPackageCount() { 
    int count = 0; 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s instanceof PackageShape) {
	  count++; 
	}
      }
    }
    return count; 
  }

  public int getRecursiveSubPackageCount() { 
    int count = 0; 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s instanceof PackageShape) {
	  PackageShape pkg = (PackageShape) s;
	  count += (1 + pkg.getRecursiveSubPackageCount()); 
	}
      }
    }
    return count; 
  }

  public java.util.List<PackageShape> getSubPackages() { 
    java.util.List<PackageShape> pkgList = null; 
    if (subshapes != null) {
      int n = subshapes.size();
      for (int i = 0; i < n; i++) {
	DrawShape s = subshapes.get(i);
	if (s instanceof PackageShape) {
	  if (pkgList == null) { 
	    pkgList = new ArrayList<PackageShape>();
	  }
	  pkgList.add((PackageShape) s); 
	}
      }
    }
    return pkgList; 
  }

  @Override
  public void positionSubNodes() {
    positionSubNodes(true); //layoutSubnodes);
  }

  public void positionSubNodes(boolean fit) {
    //if (fit || !leaf) { 
    if (fit) { 
      float left = (float) Math.min(p1.getX(), p2.getX());
      float top = (float) Math.min(p1.getY(), p2.getY());
      float width = (float) Math.abs(p1.getX() - p2.getX());
      float height = (float) Math.abs(p1.getY() - p2.getY());
      
      int role = (leaf ? DrawShapeConstants.ROLE_NODE_LABEL
		  : DrawShapeConstants.ROLE_NODE_TITLE_LABEL);
      Label header = getLabelShape(role); 
      float w0 = 0, h0 = 0;      
      if (header != null) { 
	w0 = header.getShapeWidth() + 2 * cpady;
	h0 = header.getShapeHeight() + 2 * cpady;
      } else { 
	w0 = 2 * cpady;
	h0 = 2 * cpady;
      }
      if (leaf) { 
	float w = Math.max(LEAF_MIN_WIDTH, w0 + LEAF_GAP * 2);
	float h = h0 + LEAF_GAP * 2 + LEAF_TITLE_HEIGHT;
	setEnds(left, top, left + w, top + h);
      } else {
	float titleHeight = titleShape.getShapeHeight(); 
	float titleWidth = titleShape.getShapeWidth(); 
	if (w0 > titleWidth) { 
	  titleShape.moveEdge(PointEastEdge, (w0 - titleWidth), 0);
	  titleWidth = w0;
	}

	float dx = 0;
	float dy = 0; 
	int count = getSubPackageCount();
	if (count > 0 &&
	    subshapes != null) {
	  java.util.List<PackageShape> subpkg = getSubPackages();
	  if (layoutSubnodes) { 
	    Collections.sort(subpkg, new PackageComparator());

	    java.util.List<PackageShape> subpkg1 = new ArrayList<PackageShape>();
	    java.util.List<PackageShape> subpkg2 = new ArrayList<PackageShape>();
	    java.util.List<PackageShape> subpkg3 = new ArrayList<PackageShape>();
	    for (PackageShape pkg : subpkg) {
	      if (pkg.isVisible()) { 
		pkg.doLayout();
		int d = pkg.getMaxDepth();
		int c = pkg.getRecursiveSubPackageCount();
		if (d == 1) { 
		  subpkg1.add(pkg); 
		} else if (d == 2 && c < 5) { 
		  subpkg2.add(pkg); 
		} else {
		  subpkg3.add(pkg); 
		}
	      }
	    }

	    float y = top + titleHeight + GAP; 
	    LayoutParam param = new LayoutParam();
	    param.top = y;
	    param.left = left + GAP;
	    if (subpkg3.size() > 0) { 
	      DiagramLayout.layoutGrid(subpkg3, param, 1, GAP, GAP);
	      //DiagramLayout.layoutPack(subpkg3, param, GAP, GAP, maxWidth);
	      param.top = param.bottom + GAP;
	      param.left = left + GAP;
	    }

	    if (subpkg2.size() > 0) { 
	      if (subpkg3.size() > 0) {
		float maxWidth = param.right - param.left; 
		DiagramLayout.layoutPack(subpkg2, param, GAP, GAP, maxWidth);
	      } else { 
		int n = (int) Math.ceil(Math.sqrt(count)); 
		if (n < 2) 
		  n = 2;	    
		DiagramLayout.layoutGrid(subpkg2, param, n, GAP, GAP);
	      }	    
	      param.top = param.bottom + GAP;
	      param.left = left + GAP;
	    }

	    if (subpkg1.size() > 0) { 
	      if (subpkg3.size() == 0 &&
		  subpkg2.size() == 0) {
		int n = (int) Math.ceil(Math.sqrt(count)) + 1; 
		if (n < 2) 
		  n = 2;	    
		DiagramLayout.layoutGrid(subpkg1, param, n, GAP, GAP);
	      } else {
		float maxWidth = param.right - param.left; 
		DiagramLayout.layoutPack(subpkg1, param, GAP, GAP, maxWidth);
	      }
	    }	    

	    float x2 = param.right + GAP;
	    float y2 = param.bottom + GAP;
	    if ((left + titleWidth + 2 * GAP) > x2) {
	      x2 = left + titleWidth + 2 * GAP;
	    }
	  
	    if (x2 > (left + width)) {
	      dx = (x2 - (left + width)); 
	    }
	    if (y2 > (top + height)) { 
	      dy = (y2 - (top + height));
	    }

	    if (dx > 0 || dy > 0) { 
	      setEnds(left, top, left + width + dx, top + height + dy);
	    }	  
	  } else { 
	    // no layout subnodes, but adjust the size to enusre all subnodes are contained within
	    Rectangle2D subbounds = getShapeBounds(subpkg);
	    if (subbounds != null) { 
	      float y = top + titleHeight + GAP; 
	      float x = left + GAP;

	      if (x > subbounds.getX()) { 
		dx = x - (float) subbounds.getX();
	      }
	      if (y > (float) subbounds.getY()) { 
		dy = y - (float) subbounds.getY();
	      }
	      if (dx > 0 || dy > 0) {
		moveShapes(subpkg, dx, dy);
	      }

			   
	      float x2 = (float) subbounds.getX() + (float) subbounds.getWidth() + dx + GAP; 
	      float y2 = (float) subbounds.getY() + (float) subbounds.getHeight() + dy + GAP; 
	      dx = dy = 0;
	      if (x2 > (left + width)) {
		dx = (x2 - (left + width)); 
	      }
	      if (y2 > (top + height)) { 
		dy = (y2 - (top + height));
	      }
	      
	      if (dx > 0 || dy > 0) { 
		setEnds(left, top, left + width + dx, top + height + dy);
	      }	
	    }	    
	  }
	}
      }
    }
  }

  public boolean isLeaf() { 
    return leaf; 
  }

  public void setLeaf(boolean b) { 
    leaf = b; 
  }

  public boolean isLayoutSubnodes() { 
    return layoutSubnodes;
  }

  public void setLayoutSubnodes(boolean layoutSubnodes) { 
    this.layoutSubnodes = layoutSubnodes;
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }

    attrs.put("leaf", leaf);
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("leaf");
      if (s != null) { 
	leaf = Boolean.valueOf(s);
      }
    }
  }

  protected boolean leaf = false; 

  protected boolean layoutSubnodes = false; 

  protected static final int CONTAINER_TITLE_HEIGHT = 20;

  protected static final int LEAF_TITLE_HEIGHT = 10;

  protected static final int LEAF_GAP = 5;  

  protected static final int LEAF_MIN_WIDTH = 100;
  
  protected static final int GAP = 20;  

}